<!--
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGL SpaceRocks</title>
<style>
html, body {
  width: 100%;
  height: 100%;
  border: 0px;
  padding: 0px;
  margin: 0px;
  background-color: red;
  font-family: sans-serif;
  overflow: hidden;
  color: #fff;
}
a {
 color: #fff;
}
#info {
  font-size: small;
  position: absolute;
  op: 0px; width: 100%;
  padding: 5px;
  text-align: center;
  z-index: 2;
}
CANVAS {
  background-color: gray;
}
.fpsContainer {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  color: white;
  font-family: sans-serif;
  background-color: rgba(0,0,0,0.5);
  border-radius: 10px;
  padding: 10px;
}
#viewContainer {
  width: 100%;
  height: 100%;
}
</style>
<script src="../khronos/webgl-debug.js"></script>
<script src="../tdl/base.js"></script>
<script>
tdl.require('tdl.buffers');
tdl.require('tdl.fast');
tdl.require('tdl.fps');
tdl.require('tdl.log');
tdl.require('tdl.math');
tdl.require('tdl.models');
tdl.require('tdl.particles');
tdl.require('tdl.primitives');
tdl.require('tdl.programs');
tdl.require('tdl.textures');
tdl.require('tdl.webgl');
window.onload = main;

// globals
var gl;                   // the gl context.
var canvas;               // the canvas
var math;                 // the math lib.
var fast;                 // the fast math lib.
var g_fpsTimer;           // object to measure frames per second;
var g_logGLCalls = true;  // whether or not to log webgl calls
var g_debug = false;      // whether or not to debug.
var g_drawOnce = false;   // draw just one frame.
var g_setCountElements = [];
var g_explosionMgr;
var g_debrisMgr;
var g_timeTillLaunch = 0;
var g_showShockwave = false;
var g_requestId;

//g_drawOnce = true;
//g_debug = true;

var g_numMeteors        = 50;
var g_numShockwaves     = 20;
var g_eyeSpeed          = 0.1;
var g_eyeHeight         = 2;
var g_eyeRadius         = 3;
var g_shockwaveSpeed    = 1;
var g_shockwaveDuration = 6;
var g_meteorSpeed       = 1;
var g_meteorDuration    = 3;
var g_meteorDistance    = 2;
var g_launchSpeed       = 0.5;

function ValidateNoneOfTheArgsAreUndefined(functionName, args) {
  for (var ii = 0; ii < args.length; ++ii) {
    if (args[ii] === undefined) {
      tdl.error("undefined passed to gl." + functionName + "(" +
                tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
    }
  }
}

function Log(msg) {
  if (g_logGLCalls) {
    tdl.log(msg);
  }
}

function LogGLCall(functionName, args) {
  if (g_logGLCalls) {
    ValidateNoneOfTheArgsAreUndefined(functionName, args)
    tdl.log("gl." + functionName + "(" +
                tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
  }
}

/**
 * Sets up Planet.
 */
function setupPlanet() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture('assets/rock-color.png'),
    bumpSampler: tdl.textures.loadTexture('assets/rock-nmap.png')};

  var program = tdl.programs.loadProgramFromScriptTags(
      'planetVertexShader',
      'planetFragmentShader');
  var arrays = tdl.primitives.createSphere(1, 100, 100);

  tdl.primitives.addTangentsAndBinormals(arrays);
  var model = new tdl.models.Model(program, arrays, textures);
  var img = document.createElement('img');
  img.onload = function() {
    var canvas = document.createElement('canvas');
    var width  = img.width;
    var height = img.height;
    canvas.width = width;
    canvas.height = height;
    var ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    var imgData = ctx.getImageData(0, 0, width, height);
    var pixels = imgData.data;

    function getRed(u, v) {
      var x = Math.floor(Math.min(u * width, width - 1));
      var y = Math.floor(Math.min(v * height, height - 1));
      var r = pixels[(y * width + x) * 4] / 255;
      return r;
    }

    for (var ii = 0; ii < arrays.position.numElements; ++ii) {
      var uv = arrays.texCoord.getElement(ii);
      var norm = arrays.normal.getElement(ii);
      var pos = arrays.position.getElement(ii);
      var h = (1.0 - getRed(uv[0], uv[1]) - 0.5) * 2.0 * 0.03;
      pos[0] += norm[0] * h;
      pos[1] += norm[1] * h;
      pos[2] += norm[2] * h;
      arrays.position.setElement(ii, pos);
    }
    model.setBuffer('position', arrays.position);
  }
  img.src = 'assets/height-map.png';

  return model;
}

/**
 * Sets up Ocean.
 */
function setupOcean() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture([15,100,200,200]),
    bumpSampler: tdl.textures.loadTexture([128,128,255,128])};
  var program = tdl.programs.loadProgramFromScriptTags(
      'waterVertexShader',
      'waterFragmentShader');
  var arrays = tdl.primitives.createSphere(1, 100, 100);
  tdl.primitives.addTangentsAndBinormals(arrays);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up Meteor.
 */
function setupMeteor() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture('assets/rock-color.png'),
    bumpSampler: tdl.textures.loadTexture('assets/rock-nmap.png'),
    heightSampler: tdl.textures.loadTexture('assets/height-map.png')};
  var program = tdl.programs.loadProgramFromScriptTags(
      'meteorVertexShader',
      'meteorFragmentShader');
  var arrays = tdl.primitives.createSphere(1, 8, 8);
  tdl.primitives.addTangentsAndBinormals(arrays);
//  tdl.primitives.reorient(arrays,
//      [0.6, 0, 0, 0,
//       0, 1, 0, 0,
//       0, 0, 0.8, 0,
//       0, 0, 0, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up Shield.
 */
function setupShield() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture([255,255,255,255])};
  var program = tdl.programs.loadProgramFromScriptTags(
      'shieldVertexShader',
      'shieldFragmentShader');
  var arrays = tdl.primitives.createSphere(1.1, 20, 20);
  delete arrays.normal;
  return new tdl.models.Model(program, arrays, textures, gl.LINES);
}

/**
 * Sets up ShockWave.
 */
function setupShockwave() {
  var textures = {
    shieldGradient: tdl.textures.loadTexture('assets/shield-gradient.png'),
    shieldNoise: tdl.textures.loadTexture('assets/shield-noise.png')};
  textures.shieldGradient.setParameter(gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  textures.shieldGradient.setParameter(gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  var program = tdl.programs.loadProgramFromScriptTags(
      'shockwaveVertexShader',
      'shockwaveFragmentShader');
  var arrays = tdl.primitives.createSphere(1.1, 20, 20);
  delete arrays.normal;
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0, 1, 0,
       0, -1, 0, 0,
       0, 0, 0, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up Skybox.
 */
function setupSkybox() {
  var textures = {
    skybox: tdl.textures.loadTexture([
        'assets/space_rt.jpg',
        'assets/space_lf.jpg',
        'assets/space_up.jpg',
        'assets/space_dn.jpg',
        'assets/space_fr.jpg',
        'assets/space_bk.jpg'])
  };
  var program = tdl.programs.loadProgramFromScriptTags(
      'skyboxVertexShader',
      'skyboxFragmentShader');
  var arrays = tdl.primitives.createPlane(2, 2, 1, 1);
  delete arrays['normal'];
  delete arrays['texCoord'];
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0, 1, 0,
       0,-1, 0, 0,
       0, 0, 0.99, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets the count
 */
function setCount(elem, count) {
  g_timeTillLaunch = 0;
  g_launchSpeed = count;
  for (var ii = 0; ii < g_setCountElements.length; ++ii) {
    g_setCountElements[ii].style.color = "gray";
  }
  elem.style.color = "red";
}

/**
 * Sets up the count buttons.
 */
function setupCountButtons() {
  var g_launchDurations = [
    5,
    1,
    0.5,
    0.05,
  ];
  for (var ii = 0; ii < g_launchDurations.length; ++ii) {
    var elem = document.getElementById("setCount" + ii);
    if (!elem) {
      break;
    }
    g_setCountElements.push(elem);
    elem.onclick = function(elem, count) {
      return function () {
        setCount(elem, count);
      }}(elem, g_launchDurations[ii]);
  }
  setCount(document.getElementById('setCount0'),
           g_launchDurations[0]);
}

function setupFlame(particleSystem) {
  var emitter = particleSystem.createParticleEmitter();
  emitter.setTranslation(0, 0, 0);
  emitter.setState(tdl.particles.ParticleStateIds.ADD);
  emitter.setColorRamp(
      [1, 1, 0, 1,
       1, 0, 0, 1,
       0, 0, 0, 1,
       0, 0, 0, 0.5,
       0, 0, 0, 0]);
  emitter.setParameters({
      numParticles: 20,
      lifeTime: 2,
      timeRange: 2,
      startSize: 0.5,
      endSize: 0.9,
      velocity:[0, 0.60, 0], velocityRange: [0.15, 0.15, 0.15],
      worldAcceleration: [0, -0.20, 0],
      spinSpeedRange: 4});
}

function setupDebris(particleSystem) {
  var texture = tdl.textures.loadTexture('assets/rocks.png');
  var emitter = particleSystem.createParticleEmitter(texture.texture);
  emitter.setTranslation(0, 0, 0);
  emitter.setColorRamp(
      [1, 1, 1, 1,
       1, 1, 1, 1,
       1, 1, 1, 0]);
  emitter.setParameters({
      numParticles: 10,
      numFrames: 4,
      frameDuration: 50.0,
      frameStartRange: 0,
      lifeTime: 2,
      startTime: 0,
      startSize: 0.3,
      endSize: 0.0,
      spinSpeedRange: 20},
      function(index, parameters) {
          var speed = Math.random() * 0.6 + 0.2;
          var speed2 = Math.random() * 0.2 + 0.1;
          var angle = Math.random() * 2 * Math.PI;
          parameters.velocity = math.matrix4.transformPoint(
              math.matrix4.rotationZ(angle), [speed, speed2, 0]);
      });
  return tdl.particles.createOneShotManager(emitter, 20);
}

function setupExplosion(particleSystem) {
  var emitter = particleSystem.createParticleEmitter();
  emitter.setState(tdl.particles.ParticleStateIds.ADD);
  emitter.setColorRamp(
      [1, 1, 1, 1,
       1, 1, 0, 1,
       1, 0, 0, 1,
       1, 1, 1, 0.5,
       1, 1, 1, 0]);
  emitter.setParameters({
      numParticles: 60,
      lifeTime: 1.5,
      startTime: 0,
      startSize: 0.2,
      endSize: 1.0,
      spinSpeedRange: 10},
      function(index, parameters) {
          var speed = Math.random() * 0.4 + 0.8;
          var angle = Math.random() * 2 * Math.PI;
          parameters.velocity = math.matrix4.transformPoint(
              math.matrix4.rotationZ(angle), [speed, 0, 0]);
          parameters.acceleration = math.mulVectorVector(
              parameters.velocity, [speed * -0.3, speed * -0.3, 0]);
      });
  return tdl.particles.createOneShotManager(emitter, 20);
}

function triggerExplosion(transNorm) {
    var _tp_ = new Float32Array(16);
    var _tv_ = new Float32Array(3);
    tdl.fast.addVector(_tv_, transNorm, transNorm);
    tdl.fast.matrix4.cameraLookAt(_tp_, transNorm, _tv_, [0, 1, 0]);
    g_explosionMgr.startOneShot(_tp_);
    g_debrisMgr.startOneShot(_tp_);
}

function onKeyPress(event) {
  if (event.charCode == 'd'.charCodeAt(0) ||
      event.charCode == 'D'.charCodeAt(0)) {
    g_showShockwave = !g_showShockwave;
  }
}

function main() {
  math = tdl.math;
  fast = tdl.fast;
  canvas = document.getElementById("canvas");
  g_fpsTimer = new tdl.fps.FPSTimer();

  setupCountButtons();

  window.addEventListener('keypress', onKeyPress, false);

  //canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(canvas);
  // tell the simulator when to lose context.
  //canvas.loseContextInNCalls(1);

  tdl.webgl.registerContextLostHandler(canvas, handleContextLost);
  tdl.webgl.registerContextRestoredHandler(canvas, handleContextRestored);

  gl = tdl.webgl.setupWebGL(canvas);
  if (!gl) {
    return false;
  }

  if (g_debug) {
    gl = tdl.webgl.makeDebugContext(gl, undefined, LogGLCall);
  }

  initialize();
}

function handleContextLost() {
  tdl.log("context lost");
  cancelAnimationFrame(g_requestId);
}

function handleContextRestored() {
  tdl.log("context restored");
  initialize();
}

function initialize() {
//  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, gl.TRUE);

  Log("--Setup Planet---------------------------------------");
  var planet = setupPlanet();
  Log("--Setup Ocean---------------------------------------");
  var ocean = setupOcean();
  Log("--Setup Meteor---------------------------------------");
  var meteor = setupMeteor();
  Log("--Setup Skybox---------------------------------------");
  var skybox = setupSkybox();
  Log("--Setup Shield---------------------------------------");
  var shield = setupShield();
  Log("--Setup ShockWave---------------------------------------");
  var shockwave = setupShockwave();

  var shockwaveDebugTextures = {
    shieldGradient: tdl.textures.loadTexture([255,255,255,255]),
    shieldNoise: tdl.textures.loadTexture([255,255,255,255])
  };

  particleSystem = new tdl.particles.ParticleSystem(
      gl, null, math.pseudoRandom);
  setupFlame(particleSystem);
  g_explosionMgr = setupExplosion(particleSystem);
  g_debrisMgr = setupDebris(particleSystem);

  var then = 0.0;
  var clock = 0.0;
  var fpsElem = document.getElementById("fps");

  var projection = new Float32Array(16);
  var view = new Float32Array(16);
  var world = new Float32Array(16);
  var worldInverse = new Float32Array(16);
  var worldInverseTranspose = new Float32Array(16);
  var viewProjection = new Float32Array(16);
  var worldViewProjection = new Float32Array(16);
  var viewInverse = new Float32Array(16);
  var viewDirectionProjectionInverse = new Float32Array(16);
  var eyePosition = new Float32Array(3);
  var target = new Float32Array(3);
  var up = new Float32Array([0,1,0]);
  var lightWorldPos = new Float32Array(3);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = new Float32Array(16);
  var m4t1 = new Float32Array(16);
  var m4t2 = new Float32Array(16);
  var m4t3 = new Float32Array(16);
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);

  // Sky uniforms.
  var skyConst = {
      viewDirectionProjectionInverse: viewDirectionProjectionInverse};
  var skyPer = {};

  // Planet uniforms.
  var planetConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    lightColor: one4,
    emissive: zero4,
    ambient: zero4,
    specular: one4,
    shininess: 50,
    specularFactor: 0.2};
  var planetPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};

  // Ocean uniforms.
  var oceanConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    lightColor: one4,
    emissive: zero4,
    ambient: zero4,
    specular: one4,
    shininess: 50,
    specularFactor: 1.0};
  var oceanPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};

  // Meteor uniforms.
  var meteorConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    lightColor: one4,
    emissive: zero4,
    ambient: zero4,
    specular: one4,
    shininess: 50,
    specularFactor: 0.2};
  var meteorPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};
  var freeMeteors = [];
  var meteorActive = [];
  var meteorTimer = [];
  var meteorScale = [];
  var meteorColor = [];
  var meteorVector = [];
  var meteorSpeed = [];
  var meteorAxis = [];
  var meteorRotationSpeed = [];
  for (var ii = 0; ii < g_numMeteors; ++ii) {
    freeMeteors[ii] = ii;
    meteorActive[ii] = false;
    meteorTimer[ii] = 0;
    meteorAxis[ii] = [0,0,0];
    meteorSpeed[ii] = 1;
    meteorRotationSpeed[ii] = 1;
    meteorScale[ii] = 1;
    meteorVector[ii] = new Float32Array([0, 1, 0]);
    meteorColor[ii] = new Float32Array([
        Math.random() * 0.3 + 0.7,
        Math.random() * 0.3 + 0.7,
        Math.random() * 0.3 + 0.7,
        1]);
  }

  // Shield uniforms
  var shieldConst = {
    worldViewProjection: worldViewProjection,
    colorMult: new Float32Array([1,1,1,1])};
  var shieldBackPer = {};
  var shieldFrontPer = {};
  var shieldColors = [
      new Float32Array([0.3,1,0.8,1]),
      new Float32Array([0.3,1,1,1]),
      new Float32Array([0.3,0.8,1,1])
    ];

  // Shockwave uniforms
  var shockwaveConst = {};
  var shockwavePer = {
    worldViewProjection: worldViewProjection,
    time: 0,
    voff: 0,
    colorMult: new Float32Array([1,1,1,1])};
  var freeShockwaves = [];
  var shockwaveActive = [];
  var shockwaveTimer = [];
  var shockwaveColor = [];
  var shockwavePoint = [];
  for (var ii = 0; ii < g_numShockwaves; ++ii) {
    freeShockwaves[ii] = ii;
    shockwaveActive[ii] = false;
    shockwaveTimer[ii] = 0;
    shockwavePoint[ii] = new Float32Array([0, 1, 0]);
    shockwaveColor[ii] = new Float32Array([0.2, 1, 0.2, 1]);
  }

  var frameCount = 0;
  function render() {
    ++frameCount;
    var now = (new Date()).getTime() * 0.001;
    var elapsedTime;
    if(then == 0.0) {
      elapsedTime = 0.0;
    } else {
      elapsedTime = now - then;
    }
    then = now;

    g_fpsTimer.update(elapsedTime);
    fpsElem.innerHTML = g_fpsTimer.averageFPS;

    clock += elapsedTime;
    eyePosition[0] = Math.sin(clock * g_eyeSpeed) * g_eyeRadius;
    eyePosition[1] = g_eyeHeight;
    eyePosition[2] = Math.cos(clock * g_eyeSpeed) * g_eyeRadius;

    gl.colorMask(true, true, true, true);
    gl.depthMask(true);
    gl.clearColor(0,0,0,0);
    gl.clearDepth(1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    gl.disable(gl.BLEND);
    gl.depthFunc(gl.LEQUAL);
    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

    fast.matrix4.perspective(
        projection,
        math.degToRad(60),
        canvas.clientWidth / canvas.clientHeight,
        1,
        5000);
    fast.matrix4.lookAt(
        view,
        eyePosition,
        target,
        up);
    fast.matrix4.mul(viewProjection, view, projection);
    fast.matrix4.inverse(viewInverse, view);
    fast.matrix4.copy(m4t0, view);
    fast.matrix4.setTranslation(m4t0, [0, 0, 0]);
    fast.matrix4.mul(m4t1, m4t0, projection);
    fast.matrix4.inverse(viewDirectionProjectionInverse, m4t1);

    fast.matrix4.getAxis(v3t0, viewInverse, 0); // x
    fast.matrix4.getAxis(v3t1, viewInverse, 1); // y;
    fast.matrix4.getAxis(v3t2, viewInverse, 2); // z;
    fast.mulScalarVector(v3t0, 10, v3t0);
    fast.mulScalarVector(v3t1, 10, v3t1);
    fast.mulScalarVector(v3t2, 10, v3t2);
    fast.addVector(lightWorldPos, eyePosition, v3t0);
    fast.addVector(lightWorldPos, lightWorldPos, v3t1);
    fast.addVector(lightWorldPos, lightWorldPos, v3t2);

//      view: view,
//      projection: projection,
//      viewProjection: viewProjection,

    // Draw Skybox
    skybox.drawPrep(skyConst);
    skybox.draw(skyPer);

    Log("--Draw Planet---------------------------------------");
    planet.drawPrep(planetConst);
    fast.matrix4.translation(world, [0, 0, 0]),
    fast.matrix4.mul(worldViewProjection, world, viewProjection);
    fast.matrix4.inverse(worldInverse, world);
    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
    planet.draw(planetPer);

    Log("--Draw Ocean---------------------------------------");
    gl.enable(gl.BLEND);
    ocean.drawPrep(oceanConst);
    fast.matrix4.translation(world, [0, 0, 0]),
    fast.matrix4.mul(worldViewProjection, world, viewProjection);
    fast.matrix4.inverse(worldInverse, world);
    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
    ocean.draw(oceanPer);

    Log("--Draw Meteors--------------------------------------");
    gl.disable(gl.BLEND);
    meteor.drawPrep(meteorConst);

    g_timeTillLaunch -= elapsedTime;
    if (g_timeTillLaunch <= 0) {
      g_timeTillLaunch = g_launchSpeed;
      if (freeMeteors.length > 0) {
        var ii = freeMeteors.pop();
        meteorActive[ii] = true;
        meteorTimer[ii] = g_meteorDuration;
        tdl.fast.normalize(
            meteorVector[ii],
            [Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5]);
        tdl.fast.normalize(
            meteorAxis[ii],
            [Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5]);
        meteorSpeed[ii] = Math.random() * 0.2 + 0.9;
        meteorRotationSpeed[ii] =
            (Math.random() * 0.5 + 4.5) * g_meteorDuration;
        meteorScale[ii] = [
            Math.random() * 0.2 + 0.1,
            Math.random() * 0.2 + 0.1,
            Math.random() * 0.2 + 0.1];
      }
    }

    for (var ii = 0; ii < g_numMeteors; ++ii) {
      if (!meteorActive[ii]) {
        continue;
      }
      var vector = meteorVector[ii];
      if (meteorTimer[ii] <= 0) {
        meteorActive[ii] = false;
        freeMeteors.push(ii);
        var ss = freeShockwaves.shift();
        freeShockwaves.push(ss);

        shockwaveActive[ss] = true;
        shockwaveTimer[ss] = g_shockwaveDuration;
        tdl.fast.normalize(
            shockwavePoint[ss],
            vector);

        triggerExplosion(vector);
      } else {
        meteorTimer[ii] -= elapsedTime * meteorSpeed[ii] * g_meteorSpeed;
      }
      var lerp = meteorTimer[ii] / g_meteorDuration;
      fast.mulScalarVector(v3t0, lerp * g_meteorDistance + 1.3, vector);
      fast.matrix4.translation(m4t0, v3t0);
      fast.matrix4.scaling(m4t1, meteorScale[ii]);
      fast.matrix4.axisRotation(
          m4t2, meteorAxis[ii], lerp * meteorRotationSpeed[ii]);
      fast.matrix4.mul(m4t3, m4t1, m4t2);
      fast.matrix4.mul(world, m4t3, m4t0);
      fast.matrix4.mul(worldViewProjection, world, viewProjection);
      fast.matrix4.inverse(worldInverse, world);
      fast.matrix4.transpose(worldInverseTranspose, worldInverse);
      meteor.draw(meteorPer);
    }

    Log("--Draw Shield---------------------------------------");
    gl.enable(gl.BLEND);
    gl.enable(gl.CULL_FACE);
    gl.depthMask(false);

    fast.matrix4.translation(world, [0, 0, 0]);
    fast.matrix4.mul(worldViewProjection, world, viewProjection);
    fast.matrix4.inverse(worldInverse, world);
    fast.matrix4.transpose(worldInverseTranspose, worldInverse);
    shieldConst.colorMult = shieldColors[frameCount % shieldColors.length];
    shield.drawPrep(shieldConst);

    // Draw front of class
    gl.cullFace(gl.FRONT);
    shield.draw(shieldBackPer);

    // Draw front of class
    gl.cullFace(gl.BACK);
    shield.draw(shieldFrontPer);

    Log("--Draw ShockWaves------------------------------------");
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.ONE, gl.ONE);
    gl.disable(gl.CULL_FACE);

    shockwave.drawPrep(
        shockwaveConst,
        g_showShockwave ? shockwaveDebugTextures : undefined);
    shockwave.mode = g_showShockwave ? gl.LINES : gl.TRIANGLES;

    for (var ii = 0; ii < g_numShockwaves; ++ii) {
      if (!shockwaveActive[ii]) {
        continue;
      }
      if (shockwaveTimer[ii] <= 0) {
        shockwaveActive[ii] = false;
      } else {
        shockwaveTimer[ii] -= elapsedTime * g_shockwaveSpeed;
      }
      var point = shockwavePoint[ii];
      fast.matrix4.cameraLookAt(
          world,
          [0, 0, 0],
          point,
          [point[1], point[0], point[2]]);
      fast.matrix4.mul(worldViewProjection, world, viewProjection);
      fast.matrix4.inverse(worldInverse, world);
      fast.matrix4.transpose(worldInverseTranspose, worldInverse);
      var lerp = shockwaveTimer[ii] / g_shockwaveDuration;
      var color = shockwaveColor[ii];
      var colorLerp = (lerp * 8 - 6) * 2;
      colorLerp *= (frameCount & 1) ? 1 : 0.5;
      color[0] = 2*1.2 * colorLerp;
      color[1] = 2*0.3 * colorLerp;
      color[2] = 2*0.2 * colorLerp;
      color[3] = 2*1 * colorLerp;
      shockwavePer.voff = 2 - lerp * 4;
      shockwavePer.colorMult = color;
      shockwavePer.time = shockwaveTimer[ii];
      shockwave.draw(shockwavePer);
    }

    fast.matrix4.translation(world, [0, 0, 0]);
    particleSystem.draw(viewProjection, world, viewInverse);

    // Set the alpha to 255.
    gl.colorMask(false, false, false, true);
    gl.clearColor(0,0,0,1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // turn off logging after 1 frame.
    g_logGLCalls = false;

    if (!g_drawOnce) {
      g_requestId = requestAnimationFrame(render);
    }
  }
  render();
  return true;
}
</script>
</head>
<body>
<div id="info"><a href="http://threedlibrary.googlecode.com" target="_blank">tdl.js</a> - spacerocks</div>
<div class="fpsContainer">
  <div class="fps">fps: <span id="fps"></div>
  <div>Meteors</div>
  <div id="setCount0">1</div>
  <div id="setCount1">a few</div>
  <div id="setCount2">many</div>
  <div id="setCount3">lots</div>
</div>
<div id="viewContainer">
<canvas id="canvas" width="1024" height="1024" style="width: 100%; height: 100%;"></canvas>
</div>
</body>
<!--
<script id="constVertexShader" type="text/something-not-javascript">
attribute vec4 position;
//attribute vec3 normal;
//attribute vec2 texCoord;
//varying vec2 v_texCoord;
//varying vec3 v_normal;
uniform mat4 worldViewProjection;
void main() {
  //v_texCoord = texCoord;
  //v_normal = normal;
  gl_Position = (worldViewProjection * position);
}
</script>
<script id="constFragmentShader" type="text/something-not-javascript">
precision mediump float;
//varying vec2 v_texCoord;
//varying vec3 v_normal;
void main() {
//  gl_FragColor = vec4(vec3(v_texCoord, 1) + v_normal, 1);
  gl_FragColor = vec4(1, 1, 0, 1);
}
</script>
-->
<script id="shieldVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 v_texCoord;
uniform mat4 worldViewProjection;
void main() {
  v_texCoord = texCoord;
  gl_Position = (worldViewProjection * position);
}
</script>
<script id="shieldFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D diffuseSampler;
uniform vec4 colorMult;
void main() {
  gl_FragColor = texture2D(diffuseSampler, v_texCoord) * colorMult;
}
</script>
<script id="shockwaveVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 v_texCoord;
uniform mat4 worldViewProjection;
void main() {
  v_texCoord = texCoord;
  gl_Position = (worldViewProjection * position);
}
</script>
<script id="shockwaveFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec2 v_texCoord;
uniform float voff;
uniform float time;
uniform vec4 colorMult;
uniform sampler2D shieldGradient;
uniform sampler2D shieldNoise;
void main() {
  vec4 grad = texture2D(shieldGradient,
                        vec2(0, voff) + v_texCoord * vec2(2, 2)).rgbr;
  vec4 noise1 = texture2D(shieldNoise,
                          v_texCoord + vec2(time, time) * vec2(0.0, 0.3)).rgbr;
  vec4 noise2 = texture2D(shieldNoise,
                          v_texCoord + vec2(time, time) * vec2(0.0, 0.4)).rgbr;
  gl_FragColor = grad * (noise1 * noise2) * colorMult;
}
</script>
<script id="planetVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  v_binormal = (worldInverseTranspose * vec4(binormal, 0)).xyz;
  v_tangent = (worldInverseTranspose * vec4(tangent, 0)).xyz;
  gl_Position = v_position;
}

</script>
<script id="planetFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 emissive;
uniform vec4 ambient;
uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  mat3 tangentToWorld = mat3(v_tangent,
                             v_binormal,
                             v_normal);
  vec3 tangentNormal = texture2D(bumpSampler, v_texCoord.xy).xyz -
                                 vec3(0.5, 0.5, 0.5);
  vec3 normal = (tangentToWorld * tangentNormal);
  normal = normalize(normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((emissive +
  lightColor * (ambient * diffuse + diffuse * litR.y +
                        + specular * litR.z * specularFactor)).rgb,
      diffuse.a);
}
</script>
<script id="waterVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  v_binormal = (worldInverseTranspose * vec4(binormal, 0)).xyz;
  v_tangent = (worldInverseTranspose * vec4(tangent, 0)).xyz;
  gl_Position = v_position;
}

</script>
<script id="waterFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 emissive;
uniform vec4 ambient;
uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  mat3 tangentToWorld = mat3(v_tangent,
                             v_binormal,
                             v_normal);
  vec3 tangentNormal = texture2D(bumpSampler, v_texCoord.xy).xyz -
                                 vec3(0.5, 0.5, 0.5);
  vec3 normal = (tangentToWorld * tangentNormal);
  normal = normalize(normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  vec4 spec = specular * litR.z * specularFactor;
  gl_FragColor = vec4((emissive +
  lightColor * (ambient * diffuse + diffuse * litR.y +
                        + spec)).rgb,
      diffuse.a + spec.a);
}
</script>
<script id="meteorVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  v_binormal = (worldInverseTranspose * vec4(binormal, 0)).xyz;
  v_tangent = (worldInverseTranspose * vec4(tangent, 0)).xyz;
  gl_Position = v_position;
}

</script>
<script id="meteorFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 emissive;
uniform vec4 ambient;
uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  mat3 tangentToWorld = mat3(v_tangent,
                             v_binormal,
                             v_normal);
  vec3 tangentNormal = texture2D(bumpSampler, v_texCoord.xy).xyz -
                                 vec3(0.5, 0.5, 0.5);
  vec3 normal = (tangentToWorld * tangentNormal);
  normal = normalize(normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((emissive +
  lightColor * (ambient * diffuse + diffuse * litR.y +
                        + specular * litR.z * specularFactor)).rgb,
      diffuse.a);
}
</script>
<script id="normalMapVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
attribute vec3 tangent;
attribute vec3 binormal;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  v_binormal = (worldInverseTranspose * vec4(binormal, 0)).xyz;
  v_tangent = (worldInverseTranspose * vec4(tangent, 0)).xyz;
  gl_Position = v_position;
}

</script>
<script id="normalMapFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_tangent;
varying vec3 v_binormal;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform vec4 emissive;
uniform vec4 ambient;
uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  mat3 tangentToWorld = mat3(v_tangent,
                             v_binormal,
                             v_normal);
  vec3 tangentNormal = texture2D(bumpSampler, v_texCoord.xy).xyz -
                                 vec3(0.5, 0.5, 0.5);
  vec3 normal = (tangentToWorld * tangentNormal);
  normal = normalize(normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((emissive +
  lightColor * (ambient * diffuse + diffuse * litR.y +
                        + specular * litR.z * specularFactor)).rgb,
      diffuse.a);
}
</script>
<!-- ===[ SkyBox ]============================================== -->
<script id="skyboxVertexShader" type="text/something-not-javascript">
attribute vec4 position;
varying vec4 v_position;
void main() {
  v_position = position;
  gl_Position = position;
}
</script>
<script id="skyboxFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform samplerCube skybox;
uniform mat4 viewDirectionProjectionInverse;
varying vec4 v_position;
void main() {
  vec4 t = (viewDirectionProjectionInverse * v_position);
  vec4 color = textureCube(
      skybox,
      normalize(t.xyz / t.w));
  gl_FragColor = color * color * color;
}
</script>
</html>


